/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.read.biff;

import jxl.*;
import jxl.biff.*;
import jxl.biff.CellReferenceHelper;
import jxl.biff.drawing.Chart;
import jxl.biff.drawing.Drawing;
import jxl.biff.drawing.DrawingData;
import jxl.biff.drawing.DrawingGroupObject;
import jxl.common.Logger;
import jxl.format.CellFormat;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Pattern;

/**
 * Represents a sheet within a workbook.  Provides a handle to the individual
 * cells, or lines of cells (grouped by Row or Column)
 * In order to simplify this class due to code bloat, the actual reading
 * logic has been delegated to the SheetReaderClass.  This class' main
 * responsibility is now to implement the API methods declared in the
 * Sheet interface
 */
public class SheetImpl implements Sheet {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(SheetImpl.class);

    /**
     * The excel file
     */
    private File excelFile;
    /**
     * A handle to the shared string table
     */
    private SSTRecord sharedStrings;

    /**
     * A handle to the sheet BOF record, which indicates the stream type
     */
    private BOFRecord sheetBof;

    /**
     * A handle to the workbook BOF record, which indicates the stream type
     */
    private BOFRecord workbookBof;

    /**
     * A handle to the formatting records
     */
    private FormattingRecords formattingRecords;

    /**
     * The name of this sheet
     */
    private String name;

    /**
     * The  number of rows
     */
    private int numRows;

    /**
     * The number of columns
     */
    private int numCols;

    /**
     * The cells
     */
    private Cell[][] cells;

    /**
     * The start position in the stream of this sheet
     */
    private int startPosition;

    /**
     * The list of specified (ie. non default) column widths
     */
    private ColumnInfoRecord[] columnInfos;

    /**
     * The array of row records
     */
    private RowRecord[] rowRecords;

    /**
     * The list of non-default row properties
     */
    private ArrayList rowProperties;

    /**
     * An array of column info records.  They are held this way before
     * they are transferred to the more convenient array
     */
    private ArrayList columnInfosArray;

    /**
     * A list of shared formula groups
     */
    private ArrayList sharedFormulas;

    /**
     * A list of hyperlinks on this page
     */
    private ArrayList hyperlinks;

    /**
     * A list of charts on this page
     */
    private ArrayList charts;

    /**
     * A list of drawings on this page
     */
    private ArrayList drawings;

    /**
     * A list of drawings (as opposed to comments/validation/charts) on this
     * page
     */
    private ArrayList images;

    /**
     * A list of data validations on this page
     */
    private DataValidation dataValidation;

    /**
     * A list of merged cells on this page
     */
    private Range[] mergedCells;

    /**
     * Indicates whether the columnInfos array has been initialized
     */
    private boolean columnInfosInitialized;

    /**
     * Indicates whether the rowRecords array has been initialized
     */
    private boolean rowRecordsInitialized;

    /**
     * Indicates whether or not the dates are based around the 1904 date system
     */
    private boolean nineteenFour;

    /**
     * The workspace options
     */
    private WorkspaceInformationRecord workspaceOptions;

    /**
     * The hidden flag
     */
    private boolean hidden;

    /**
     * The environment specific print record
     */
    private PLSRecord plsRecord;

    /**
     * The property set record associated with this workbook
     */
    private ButtonPropertySetRecord buttonPropertySet;

    /**
     * The sheet settings
     */
    private SheetSettings settings;

    /**
     * The horizontal page breaks contained on this sheet
     */
    private int[] rowBreaks;

    /**
     * The vertical page breaks contained on this sheet
     */
    private int[] columnBreaks;

    /**
     * The maximum row outline level
     */
    private int maxRowOutlineLevel;

    /**
     * The maximum column outline level
     */
    private int maxColumnOutlineLevel;

    /**
     * The list of local names for this sheet
     */
    private ArrayList localNames;

    /**
     * The list of conditional formats for this sheet
     */
    private ArrayList conditionalFormats;

    /**
     * The autofilter information
     */
    private AutoFilter autoFilter;

    /**
     * A handle to the workbook which contains this sheet.  Some of the records
     * need this in order to reference external sheets
     */
    private WorkbookParser workbook;

    /**
     * A handle to the workbook settings
     */
    private WorkbookSettings workbookSettings;

    /**
     * Constructor
     *
     * @param f the excel file
     * @param sst the shared string table
     * @param fr formatting records
     * @param sb the bof record which indicates the start of the sheet
     * @param wb the bof record which indicates the start of the sheet
     * @param nf the 1904 flag
     * @param wp the workbook which this sheet belongs to
     * @exception BiffException
     */
    SheetImpl(File f,
              SSTRecord sst,
              FormattingRecords fr,
              BOFRecord sb,
              BOFRecord wb,
              boolean nf,
              WorkbookParser wp)
            throws BiffException {
        excelFile = f;
        sharedStrings = sst;
        formattingRecords = fr;
        sheetBof = sb;
        workbookBof = wb;
        columnInfosArray = new ArrayList();
        sharedFormulas = new ArrayList();
        hyperlinks = new ArrayList();
        rowProperties = new ArrayList(10);
        columnInfosInitialized = false;
        rowRecordsInitialized = false;
        nineteenFour = nf;
        workbook = wp;
        workbookSettings = workbook.getSettings();

        // Mark the position in the stream, and then skip on until the end
        startPosition = f.getPos();

        if (sheetBof.isChart()) {
            // Set the start pos to include the bof so the sheet reader can handle it
            startPosition -= (sheetBof.getLength() + 4);
        }

        Record r = null;
        int bofs = 1;

        while (bofs >= 1) {
            r = f.next();

            // use this form for quick performance
            if (r.getCode() == Type.EOF.value) {
                bofs--;
            }

            if (r.getCode() == Type.BOF.value) {
                bofs++;
            }
        }
    }

    /**
     * Returns the cell for the specified location eg. "A4", using the
     * CellReferenceHelper
     *
     * @param loc the cell reference
     * @return the cell at the specified co-ordinates
     */
    public Cell getCell(String loc) {
        return getCell(CellReferenceHelper.getColumn(loc),
                CellReferenceHelper.getRow(loc));
    }

    /**
     * Returns the cell specified at this row and at this column
     *
     * @param row the row number
     * @param column the column number
     * @return the cell at the specified co-ordinates
     */
    public Cell getCell(int column, int row) {
        // just in case this has been cleared, but something else holds
        // a reference to it
        if (cells == null) {
            readSheet();
        }

        Cell c = cells[row][column];

        if (c == null) {
            c = new EmptyCell(column, row);
            cells[row][column] = c;
        }

        return c;
    }

    /**
     * Gets the cell whose contents match the string passed in.
     * If no match is found, then null is returned.  The search is performed
     * on a row by row basis, so the lower the row number, the more
     * efficiently the algorithm will perform
     *
     * @param  contents the string to match
     * @return the Cell whose contents match the paramter, null if not found
     */
    public Cell findCell(String contents) {
        CellFinder cellFinder = new CellFinder(this);
        return cellFinder.findCell(contents);
    }

    /**
     * Gets the cell whose contents match the string passed in.
     * If no match is found, then null is returned.  The search is performed
     * on a row by row basis, so the lower the row number, the more
     * efficiently the algorithm will perform
     *
     * @param contents the string to match
     * @param firstCol the first column within the range
     * @param firstRow the first row of the range
     * @param lastCol the last column within the range
     * @param lastRow the last row within the range
     * @param reverse indicates whether to perform a reverse search or not
     * @return the Cell whose contents match the parameter, null if not found
     */
    public Cell findCell(String contents,
                         int firstCol,
                         int firstRow,
                         int lastCol,
                         int lastRow,
                         boolean reverse) {
        CellFinder cellFinder = new CellFinder(this);
        return cellFinder.findCell(contents,
                firstCol,
                firstRow,
                lastCol,
                lastRow,
                reverse);
    }

    /**
     * Gets the cell whose contents match the regular expressionstring passed in.
     * If no match is found, then null is returned.  The search is performed
     * on a row by row basis, so the lower the row number, the more
     * efficiently the algorithm will perform
     *
     * @param pattern the regular expression string to match
     * @param firstCol the first column within the range
     * @param firstRow the first row of the range
     * @param lastRow the last row within the range
     * @param lastCol the last column within the ranage
     * @param reverse indicates whether to perform a reverse search or not
     * @return the Cell whose contents match the parameter, null if not found
     */
    public Cell findCell(Pattern pattern,
                         int firstCol,
                         int firstRow,
                         int lastCol,
                         int lastRow,
                         boolean reverse) {
        CellFinder cellFinder = new CellFinder(this);
        return cellFinder.findCell(pattern,
                firstCol,
                firstRow,
                lastCol,
                lastRow,
                reverse);
    }

    /**
     * Gets the cell whose contents match the string passed in.
     * If no match is found, then null is returned.  The search is performed
     * on a row by row basis, so the lower the row number, the more
     * efficiently the algorithm will perform.  This method differs
     * from the findCell methods in that only cells with labels are
     * queried - all numerical cells are ignored.  This should therefore
     * improve performance.
     *
     * @param  contents the string to match
     * @return the Cell whose contents match the paramter, null if not found
     */
    public LabelCell findLabelCell(String contents) {
        CellFinder cellFinder = new CellFinder(this);
        return cellFinder.findLabelCell(contents);
    }

    /**
     * Returns the number of rows in this sheet
     *
     * @return the number of rows in this sheet
     */
    public int getRows() {
        // just in case this has been cleared, but something else holds
        // a reference to it
        if (cells == null) {
            readSheet();
        }

        return numRows;
    }

    /**
     * Returns the number of columns in this sheet
     *
     * @return the number of columns in this sheet
     */
    public int getColumns() {
        // just in case this has been cleared, but something else holds
        // a reference to it
        if (cells == null) {
            readSheet();
        }

        return numCols;
    }

    /**
     * Gets all the cells on the specified row.  The returned array will
     * be stripped of all trailing empty cells
     *
     * @param row the rows whose cells are to be returned
     * @return the cells on the given row
     */
    public Cell[] getRow(int row) {
        // just in case this has been cleared, but something else holds
        // a reference to it
        if (cells == null) {
            readSheet();
        }

        // Find the last non-null cell
        boolean found = false;
        int col = numCols - 1;
        while (col >= 0 && !found) {
            if (cells[row][col] != null) {
                found = true;
            } else {
                col--;
            }
        }

        // Only create entries for non-null cells
        Cell[] c = new Cell[col + 1];

        for (int i = 0; i <= col; i++) {
            c[i] = getCell(i, row);
        }
        return c;
    }

    /**
     * Gets all the cells on the specified column.  The returned array
     * will be stripped of all trailing empty cells
     *
     * @param col the column whose cells are to be returned
     * @return the cells on the specified column
     */
    public Cell[] getColumn(int col) {
        // just in case this has been cleared, but something else holds
        // a reference to it
        if (cells == null) {
            readSheet();
        }

        // Find the last non-null cell
        boolean found = false;
        int row = numRows - 1;
        while (row >= 0 && !found) {
            if (cells[row][col] != null) {
                found = true;
            } else {
                row--;
            }
        }

        // Only create entries for non-null cells
        Cell[] c = new Cell[row + 1];

        for (int i = 0; i <= row; i++) {
            c[i] = getCell(col, i);
        }
        return c;
    }

    /**
     * Gets the name of this sheet
     *
     * @return the name of the sheet
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the name of this sheet
     *
     * @param s the sheet name
     */
    final void setName(String s) {
        name = s;
    }

    /**
     * Determines whether the sheet is hidden
     *
     * @return whether or not the sheet is hidden
     * @deprecated in favour of the getSettings function
     */
    public boolean isHidden() {
        return hidden;
    }

    /**
     * Gets the column info record for the specified column.  If no
     * column is specified, null is returned
     *
     * @param col the column
     * @return the ColumnInfoRecord if specified, NULL otherwise
     */
    public ColumnInfoRecord getColumnInfo(int col) {
        if (!columnInfosInitialized) {
            // Initialize the array
            Iterator i = columnInfosArray.iterator();
            ColumnInfoRecord cir = null;
            while (i.hasNext()) {
                cir = (ColumnInfoRecord) i.next();

                int startcol = Math.max(0, cir.getStartColumn());
                int endcol = Math.min(columnInfos.length - 1, cir.getEndColumn());

                for (int c = startcol; c <= endcol; c++) {
                    columnInfos[c] = cir;
                }

                if (endcol < startcol) {
                    columnInfos[startcol] = cir;
                }
            }

            columnInfosInitialized = true;
        }

        return col < columnInfos.length ? columnInfos[col] : null;
    }

    /**
     * Gets all the column info records
     *
     * @return the ColumnInfoRecordArray
     */
    public ColumnInfoRecord[] getColumnInfos() {
        // Just chuck all the column infos we have into an array
        ColumnInfoRecord[] infos = new ColumnInfoRecord[columnInfosArray.size()];
        for (int i = 0; i < columnInfosArray.size(); i++) {
            infos[i] = (ColumnInfoRecord) columnInfosArray.get(i);
        }

        return infos;
    }

    /**
     * Sets the visibility of this sheet
     *
     * @param h hidden flag
     */
    final void setHidden(boolean h) {
        hidden = h;
    }

    /**
     * Clears out the array of cells.  This is done for memory allocation
     * reasons when reading very large sheets
     */
    final void clear() {
        cells = null;
        mergedCells = null;
        columnInfosArray.clear();
        sharedFormulas.clear();
        hyperlinks.clear();
        columnInfosInitialized = false;

        if (!workbookSettings.getGCDisabled()) {
            System.gc();
        }
    }

    /**
     * Reads in the contents of this sheet
     */
    final void readSheet() {
        // If this sheet contains only a chart, then set everything to
        // empty and do not bother parsing the sheet
        // Thanks to steve.brophy for spotting this
        if (!sheetBof.isWorksheet()) {
            numRows = 0;
            numCols = 0;
            cells = new Cell[0][0];
            //      return;
        }

        SheetReader reader = new SheetReader(excelFile,
                sharedStrings,
                formattingRecords,
                sheetBof,
                workbookBof,
                nineteenFour,
                workbook,
                startPosition,
                this);
        reader.read();

        // Take stuff that was read in
        numRows = reader.getNumRows();
        numCols = reader.getNumCols();
        cells = reader.getCells();
        rowProperties = reader.getRowProperties();
        columnInfosArray = reader.getColumnInfosArray();
        hyperlinks = reader.getHyperlinks();
        conditionalFormats = reader.getConditionalFormats();
        autoFilter = reader.getAutoFilter();
        charts = reader.getCharts();
        drawings = reader.getDrawings();
        dataValidation = reader.getDataValidation();
        mergedCells = reader.getMergedCells();
        settings = reader.getSettings();
        settings.setHidden(hidden);
        rowBreaks = reader.getRowBreaks();
        columnBreaks = reader.getColumnBreaks();
        workspaceOptions = reader.getWorkspaceOptions();
        plsRecord = reader.getPLS();
        buttonPropertySet = reader.getButtonPropertySet();
        maxRowOutlineLevel = reader.getMaxRowOutlineLevel();
        maxColumnOutlineLevel = reader.getMaxColumnOutlineLevel();

        reader = null;

        if (!workbookSettings.getGCDisabled()) {
            System.gc();
        }

        if (columnInfosArray.size() > 0) {
            ColumnInfoRecord cir = (ColumnInfoRecord)
                    columnInfosArray.get(columnInfosArray.size() - 1);
            columnInfos = new ColumnInfoRecord[cir.getEndColumn() + 1];
        } else {
            columnInfos = new ColumnInfoRecord[0];
        }

        // Add any local names
        if (localNames != null) {
            for (Iterator it = localNames.iterator(); it.hasNext(); ) {
                NameRecord nr = (NameRecord) it.next();
                if (nr.getBuiltInName() == BuiltInName.PRINT_AREA) {
                    if (nr.getRanges().length > 0) {
                        NameRecord.NameRange rng = nr.getRanges()[0];
                        settings.setPrintArea(rng.getFirstColumn(),
                                rng.getFirstRow(),
                                rng.getLastColumn(),
                                rng.getLastRow());
                    }
                } else if (nr.getBuiltInName() == BuiltInName.PRINT_TITLES) {
                    // There can be 1 or 2 entries.
                    // Row entries have hardwired column entries (first and last
                    //  possible column)
                    // Column entries have hardwired row entries (first and last
                    // possible row)
                    for (int i = 0; i < nr.getRanges().length; i++) {
                        NameRecord.NameRange rng = nr.getRanges()[i];
                        if (rng.getFirstColumn() == 0 && rng.getLastColumn() == 255) {
                            settings.setPrintTitlesRow(rng.getFirstRow(),
                                    rng.getLastRow());
                        } else {
                            settings.setPrintTitlesCol(rng.getFirstColumn(),
                                    rng.getLastColumn());
                        }
                    }
                }
            }
        }
    }

    /**
     * Gets the hyperlinks on this sheet
     *
     * @return an array of hyperlinks
     */
    public Hyperlink[] getHyperlinks() {
        Hyperlink[] hl = new Hyperlink[hyperlinks.size()];

        for (int i = 0; i < hyperlinks.size(); i++) {
            hl[i] = (Hyperlink) hyperlinks.get(i);
        }

        return hl;
    }

    /**
     * Gets the cells which have been merged on this sheet
     *
     * @return an array of range objects
     */
    public Range[] getMergedCells() {
        if (mergedCells == null) {
            return new Range[0];
        }

        return mergedCells;
    }

    /**
     * Gets the non-default rows.  Used when copying spreadsheets
     *
     * @return an array of row properties
     */
    public RowRecord[] getRowProperties() {
        RowRecord[] rp = new RowRecord[rowProperties.size()];
        for (int i = 0; i < rp.length; i++) {
            rp[i] = (RowRecord) rowProperties.get(i);
        }

        return rp;
    }

    /**
     * Gets the data validations.  Used when copying sheets
     *
     * @return the data validations
     */
    public DataValidation getDataValidation() {
        return dataValidation;
    }

    /**
     * Gets the row record.  Usually called by the cell in the specified
     * row in order to determine its size
     *
     * @param r the row
     * @return the RowRecord for the specified row
     */
    RowRecord getRowInfo(int r) {
        if (!rowRecordsInitialized) {
            rowRecords = new RowRecord[getRows()];
            Iterator i = rowProperties.iterator();

            int rownum = 0;
            RowRecord rr = null;
            while (i.hasNext()) {
                rr = (RowRecord) i.next();
                rownum = rr.getRowNumber();
                if (rownum < rowRecords.length) {
                    rowRecords[rownum] = rr;
                }
            }

            rowRecordsInitialized = true;
        }

        return r < rowRecords.length ? rowRecords[r] : null;
    }

    /**
     * Gets the row breaks.  Called when copying sheets
     *
     * @return the explicit row breaks
     */
    public final int[] getRowPageBreaks() {
        return rowBreaks;
    }

    /**
     * Gets the row breaks.  Called when copying sheets
     *
     * @return the explicit row breaks
     */
    public final int[] getColumnPageBreaks() {
        return columnBreaks;
    }

    /**
     * Gets the charts.  Called when copying sheets
     *
     * @return the charts on this page
     */
    public final Chart[] getCharts() {
        Chart[] ch = new Chart[charts.size()];

        for (int i = 0; i < ch.length; i++) {
            ch[i] = (Chart) charts.get(i);
        }
        return ch;
    }

    /**
     * Gets the drawings.  Called when copying sheets
     *
     * @return the drawings on this page
     */
    public final DrawingGroupObject[] getDrawings() {
        DrawingGroupObject[] dr = new DrawingGroupObject[drawings.size()];
        dr = (DrawingGroupObject[]) drawings.toArray(dr);
        return dr;
    }

    /**
     * Determines whether the sheet is protected
     *
     * @return whether or not the sheet is protected
     * @deprecated in favour of the getSettings() api
     */
    public boolean isProtected() {
        return settings.isProtected();
    }

    /**
     * Gets the workspace options for this sheet.  Called during the copy
     * process
     *
     * @return the workspace options
     */
    public WorkspaceInformationRecord getWorkspaceOptions() {
        return workspaceOptions;
    }

    /**
     * Accessor for the sheet settings
     *
     * @return the settings for this sheet
     */
    public SheetSettings getSettings() {
        return settings;
    }

    /**
     * Accessor for the workbook.  In addition to be being used by this package,
     * it is also used during the importSheet process
     *
     * @return the workbook
     */
    public WorkbookParser getWorkbook() {
        return workbook;
    }

    /**
     * Gets the column format for the specified column
     *
     * @param col the column number
     * @return the column format, or NULL if the column has no specific format
     * @deprecated use getColumnView instead
     */
    public CellFormat getColumnFormat(int col) {
        CellView cv = getColumnView(col);
        return cv.getFormat();
    }

    /**
     * Gets the column width for the specified column
     *
     * @param col the column number
     * @return the column width, or the default width if the column has no
     *         specified format
     */
    public int getColumnWidth(int col) {
        return getColumnView(col).getSize() / 256;
    }

    /**
     * Gets the column width for the specified column
     *
     * @param col the column number
     * @return the column format, or the default format if no override is
    specified
     */
    public CellView getColumnView(int col) {
        ColumnInfoRecord cir = getColumnInfo(col);
        CellView cv = new CellView();

        if (cir != null) {
            cv.setDimension(cir.getWidth() / 256); //deprecated
            cv.setSize(cir.getWidth());
            cv.setHidden(cir.getHidden());
            cv.setFormat(formattingRecords.getXFRecord(cir.getXFIndex()));
        } else {
            cv.setDimension(settings.getDefaultColumnWidth()); //deprecated
            cv.setSize(settings.getDefaultColumnWidth() * 256);
        }

        return cv;
    }

    /**
     * Gets the row height for the specified column
     *
     * @param row the row number
     * @return the row height, or the default height if the row has no
     *         specified format
     * @deprecated use getRowView instead
     */
    public int getRowHeight(int row) {
        return getRowView(row).getDimension();
    }

    /**
     * Gets the row view for the specified row
     *
     * @param row the row number
     * @return the row format, or the default format if no override is
    specified
     */
    public CellView getRowView(int row) {
        RowRecord rr = getRowInfo(row);

        CellView cv = new CellView();

        if (rr != null) {
            cv.setDimension(rr.getRowHeight()); //deprecated
            cv.setSize(rr.getRowHeight());
            cv.setHidden(rr.isCollapsed());
            if (rr.hasDefaultFormat()) {
                cv.setFormat(formattingRecords.getXFRecord(rr.getXFIndex()));
            }
        } else {
            cv.setDimension(settings.getDefaultRowHeight());
            cv.setSize(settings.getDefaultRowHeight()); //deprecated
        }

        return cv;
    }


    /**
     * Used when copying sheets in order to determine the type of this sheet
     *
     * @return the BOF Record
     */
    public BOFRecord getSheetBof() {
        return sheetBof;
    }

    /**
     * Used when copying sheets in order to determine the type of the containing
     * workboook
     *
     * @return the workbook BOF Record
     */
    public BOFRecord getWorkbookBof() {
        return workbookBof;
    }

    /**
     * Accessor for the environment specific print record, invoked when
     * copying sheets
     *
     * @return the environment specific print record
     */
    public PLSRecord getPLS() {
        return plsRecord;
    }

    /**
     * Accessor for the button property set, used during copying
     *
     * @return the button property set
     */
    public ButtonPropertySetRecord getButtonPropertySet() {
        return buttonPropertySet;
    }

    /**
     * Accessor for the number of images on the sheet
     *
     * @return the number of images on this sheet
     */
    public int getNumberOfImages() {
        if (images == null) {
            initializeImages();
        }

        return images.size();
    }

    /**
     * Accessor for the image
     *
     * @param i the 0 based image number
     * @return the image at the specified position
     */
    public Image getDrawing(int i) {
        if (images == null) {
            initializeImages();
        }

        return (Image) images.get(i);
    }

    /**
     * Initializes the images
     */
    private void initializeImages() {
        if (images != null) {
            return;
        }

        images = new ArrayList();
        DrawingGroupObject[] dgos = getDrawings();

        for (int i = 0; i < dgos.length; i++) {
            if (dgos[i] instanceof Drawing) {
                images.add(dgos[i]);
            }
        }
    }

    /**
     * Used by one of the demo programs for debugging purposes only
     */
    public DrawingData getDrawingData() {
        SheetReader reader = new SheetReader(excelFile,
                sharedStrings,
                formattingRecords,
                sheetBof,
                workbookBof,
                nineteenFour,
                workbook,
                startPosition,
                this);
        reader.read();
        return reader.getDrawingData();
    }

    /**
     * Adds a local name to this shate
     *
     * @param nr the local name to add
     */
    void addLocalName(NameRecord nr) {
        if (localNames == null) {
            localNames = new ArrayList();
        }

        localNames.add(nr);
    }

    /**
     * Gets the conditional formats
     *
     * @return the conditional formats
     */
    public ConditionalFormat[] getConditionalFormats() {
        ConditionalFormat[] formats =
                new ConditionalFormat[conditionalFormats.size()];
        formats = (ConditionalFormat[]) conditionalFormats.toArray(formats);
        return formats;
    }

    /**
     * Returns the autofilter
     *
     * @return the autofilter
     */
    public AutoFilter getAutoFilter() {
        return autoFilter;
    }

    /**
     * Accessor for the maximum column outline level.  Used during a copy
     *
     * @return the maximum column outline level, or 0 if no outlines/groups
     */
    public int getMaxColumnOutlineLevel() {
        return maxColumnOutlineLevel;
    }

    /**
     * Accessor for the maximum row outline level.  Used during a copy
     *
     * @return the maximum row outline level, or 0 if no outlines/groups
     */
    public int getMaxRowOutlineLevel() {
        return maxRowOutlineLevel;
    }

}
